#include "backtrace.h"

static char g_buff[1024] = {"\0"};

void stackdump_impl(lua_State*L, int level) {
	lua_Debug debug;
	int ret = lua_getstack(L, level, &debug);
	if (1 != ret) {
		return;
	}
	lua_getinfo(L, "nfSlu", &debug);
	lua_pop(L, 1);
	int pos = strlen(g_buff);
	if (debug.what[0] == 'C') {
		if (debug.name) {
			sprintf(g_buff + pos, "%4d[C] : in %s\n", level, debug.name);
		} else {
			sprintf(g_buff + pos, "%4d[C] :\n", level);
		}
	} else if (debug.name) {
		sprintf(g_buff + pos, "%4d %s:%d in %s\n", level, debug.short_src, debug.currentline, debug.name);
	}
	else {
		sprintf(g_buff + pos, "%4d %s:%d\n", level, debug.short_src, debug.currentline);
	}
	pos = strlen(g_buff);

	int index = 1;
	int type;
	const char* key = NULL;
	const char* tpname = NULL;
	while ((key = lua_getlocal(L, &debug, index++)) != NULL) {
		if (strcmp(key, "_ENV") == 0 || strcmp(key, "_G") == 0) {
			continue;
		}
		type = lua_type(L, -1);
		if (type == LUA_TNIL) {
			continue;
		}
		tpname = lua_typename(L, type);
		switch (type) {
		case LUA_TBOOLEAN:
			sprintf(g_buff + strlen(g_buff), "\t%s = <%s> %s\n", key, tpname, lua_toboolean(L, -1) ? "true" : "false");
			break;
		case LUA_TLIGHTUSERDATA:
		case LUA_TUSERDATA:
		case LUA_TTHREAD:
		case LUA_TFUNCTION:
		case LUA_TTABLE:
			sprintf(g_buff + strlen(g_buff), "\t%s = <%s> 0x%08p\n", key, tpname, lua_topointer(L, -1));
			break;
		default:
			sprintf(g_buff + strlen(g_buff), "\t%s = <%s> %s\n", key, tpname, lua_tostring(L, -1));
			break;
		}
		lua_pop(L, 1);
	}
	index = 1;
	while ((key = lua_getupvalue(L, -1, index++)) != NULL) {
		if (strcmp(key, "_ENV") == 0 || strcmp(key, "_G") == 0) {
			continue;
		}
		type = lua_type(L, -1);
		if (type == LUA_TNIL) {
			continue;
		}
		tpname = lua_typename(L, type);
		switch (type) {
		case LUA_TBOOLEAN:
			sprintf(g_buff + strlen(g_buff), "\t%s = <%s> %s\n", key, tpname, lua_toboolean(L, -1) ? "true" : "false");
			break;
		case LUA_TLIGHTUSERDATA:
		case LUA_TUSERDATA:
		case LUA_TTHREAD:
		case LUA_TFUNCTION:
		case LUA_TTABLE:
			sprintf(g_buff + strlen(g_buff), "\t%s = <%s> 0x%08p\n", key, tpname, lua_topointer(L, -1));
			break;
		default:
			sprintf(g_buff + strlen(g_buff), "\t%s = <%s> %s\n", key, tpname, lua_tostring(L, -1));
			break;
		}
		lua_pop(L, 1);
	}
	stackdump_impl(L, level + 1);
}

int stackdump(lua_State *L) {
	g_buff[0] = '\0';
	int top = lua_gettop(L);
	if (top > 0) {
		sprintf(g_buff, "%s\n", lua_tostring(L, -1));
	}
	stackdump_impl(L, 1);
	fprintf(stdout, "%s\n", g_buff);
	// lua_pushlightuserdata(L, &g_buff);
	// lua_pushnumber(L, strlen(g_buff));
	// return 2;
	lua_pushstring(L, g_buff);
	return 1;
}

int traceback(lua_State* L, const char* err) {
	printf("got lua exception\n");
	lua_getglobal(L, "debug");
	if (!lua_istable(L, -1)) {
		printf("can't find lua debug lib\n");
		lua_pop(L, 1);
		return 1;
	}
	lua_getfield(L, -1, "excepthook");
	if (!lua_isfunction(L, -1)) {
		lua_pop(L, 2);
		printf("can't find lua debug excepthook;\n");
		return 1;
	}
	if (!err) {
		lua_pushvalue(L, 1);  //pass error message
	} else {
		lua_pushstring(L, err);
	}
	lua_pcall(L, 1, 1, 0);  //call debug.traceback

	return 1;
}

int lua_xpcall(lua_State* L, int narg, int nresults, int) {
	int base = lua_gettop(L) - narg;  //function index
	lua_pushcfunction(L, stackdump);  //push traceback function
	lua_insert(L, base);  //put it under chunk and args
	int status = lua_pcall(L, narg, nresults, base);
	lua_remove(L, base);  //remove traceback function
	return status;
}
